package cn.huanzi.qch.baseadmin.config.security;

import cn.huanzi.qch.baseadmin.common.pojo.ParameterRequestWrapper;
import cn.huanzi.qch.baseadmin.common.pojo.Result;
import cn.huanzi.qch.baseadmin.util.*;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.anji.captcha.util.StreamUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.HashMap;

/**
 * 校验账号、密码前，先进行验证码处理，需要在这里进行登录解密操作
 */
@Component
@Slf4j
public class CaptchaFilterConfig implements Filter {

    @Autowired
    private CaptchaService captchaService;

    @Value("${server.servlet.context-path:}")
    private String contextPath;

    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private UserConfig userConfig;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpSession session = request.getSession();
        String requestURI = request.getRequestURI();
        /*
            注：详情可在SessionManagementFilter中进行断点调试查看
            security框架会在session的attribute存储登录信息，先从session.getAttribute(this.springSecurityContextKey)中获取登录用户信息
            ，如果没有，再从本地上下文SecurityContextHolder.getContext().getAuthentication()获取，因此想要强制用户下线得进行如下操作

            另外，虽然重启了服务，sessionRegistry.getAllSessions()为空，但之前的用户session未过期同样能访问系统，也是这个原因
         */
        User user = securityUtil.sessionRegistryGetUserBySessionId(session.getId());
        if(user == null && session.getAttribute("SPRING_SECURITY_CONTEXT") != null){

            //remember me？
            Cookie rememberMeCookie = SecurityUtil.getRememberMeCookie(request);
            PersistentRememberMeToken token = securityUtil.rememberMeGetTokenForSeries(rememberMeCookie);

            if(!StringUtils.isEmpty(token)){
                log.info("当前session连接开启了免登陆，已自动登录！token："+rememberMeCookie.getValue() + ",userName：" + token.getUsername() + "，最后登录时间：" + token.getDate());
                //注册新的session
                securityUtil.sessionRegistryAddUser(session.getId(),userConfig.loadUserByUsername(token.getUsername()));
            }

            //当前URL是否允许访问，同时没有remember me
            if(!SecurityUtil.checkUrl(requestURI.replaceFirst(contextPath,"")) && StringUtils.isEmpty(token)){
                //直接输出js脚本跳转强制用户下线
                response.setContentType("text/html;charset=UTF-8");
                PrintWriter out = response.getWriter();
                out.print("<script type='text/javascript'>window.location.href = '" + contextPath + "/logout'</script>");
                out.flush();
                out.close();
                response.flushBuffer();
                return;
            }

        }

        if (requestURI.replaceFirst(contextPath,"").startsWith("/captcha")) {
            try {

                if ("Y".equals(SysSettingUtil.getSysSetting().getSysApiEncrypt())) {
                    String reqEncBody = URLDecoder.decode(getRequestPostStr(request),"UTF-8");
                    if ( StrUtil.isNotBlank(reqEncBody)) {
                        //AES加密后的数据
                        //String data = request.getParameter("data");
                        String data = ReUtil.get("(^|&)data=([^&]*)(&|$)", reqEncBody, 2);
                        //后端RSA公钥加密后的AES的key
                        //String aesKey = request.getParameter("aesKey");
                        String aesKey = ReUtil.get("(^|&)aesKey=([^&]*)(&|$)", reqEncBody, 2);

                        if (StrUtil.isNotBlank(data) && StrUtil.isNotBlank(aesKey)) {
                            //后端私钥解密的到AES的key
                            byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(aesKey), RsaUtil.getPrivateKey());
                            aesKey = new String(plaintext);
                            String decrypt = AesUtil.decrypt(data, aesKey);

                            ModifyBodyHttpServletRequestWrapper httpServletRequestWrapper = new ModifyBodyHttpServletRequestWrapper((HttpServletRequest) request, decrypt);
                            servletRequest = httpServletRequestWrapper;
                            request = (HttpServletRequest) servletRequest;
                        }
                    }
                }
            }catch (Exception e) {
                e.printStackTrace();
            }

        }

        //只拦截登录请求，且开发环境下不拦截
        if ("POST".equals(request.getMethod()) && "/login".equals(requestURI.replaceFirst(contextPath,""))) {
            //前端公钥
            String publicKey = null;

            //判断api加密开关是否开启
            if("Y".equals(SysSettingUtil.getSysSetting().getSysApiEncrypt())){
                //解密
                try {
                    //api解密
                    String decrypt = ApiSecurityUtil.decrypt();

                    //new一个自定义RequestWrapper
                    HashMap hashMap = JsonUtil.parse(decrypt, HashMap.class);
                    ParameterRequestWrapper parameterRequestWrapper = new ParameterRequestWrapper(request);
                    for (Object key : hashMap.keySet()) {
                        parameterRequestWrapper.addParameter(String.valueOf(key),  hashMap.get(key));
                    }

                    servletRequest = parameterRequestWrapper;
                    request = (HttpServletRequest) servletRequest;
                } catch (Throwable e) {
                    //输出到日志文件中
                    log.error(ErrorUtil.errorInfoToString(e));
                }
            }

            if ("Y".equals(SysSettingUtil.getSysSetting().getSysCaptchaEnable())) {

                String dataString = "{\"code\":\"400\",\"msg\":\"验证码错误\"}";
                CaptchaVO captchaVo = new CaptchaVO();
                captchaVo.setCaptchaVerification(request.getParameter("captcha"));
                ResponseModel responseModel = captchaService.verification(captchaVo);

                if (!responseModel.isSuccess()) {

                    //判断api加密开关是否开启
                    if ("Y".equals(SysSettingUtil.getSysSetting().getSysApiEncrypt())) {
                        //加密
                        try {
                            //api加密
                            Result encrypt = ApiSecurityUtil.encrypt(dataString);

                            dataString = JsonUtil.stringify(encrypt);
                        } catch (Throwable e) {
                            //输出到日志文件中
                            log.error(ErrorUtil.errorInfoToString(e));
                        }
                    }

                    //转json字符串并转成Object对象，设置到Result中并赋值给返回值o
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    PrintWriter out = response.getWriter();
                    out.print(dataString);
                    out.flush();
                    out.close();
                    response.flushBuffer();
                    return;
                }
            }
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    public String getRequestPostStr(HttpServletRequest request)
            throws IOException {
        String charSetStr = request.getCharacterEncoding();
        if (charSetStr == null) {
            charSetStr = "UTF-8";
        }
        Charset charSet = Charset.forName(charSetStr);

        return StreamUtils.copyToString(request.getInputStream(), charSet);
    }

    /**
     * 重写request，获取body数据的时候读取新的body
     **/
    private class ModifyBodyHttpServletRequestWrapper extends HttpServletRequestWrapper {

        // 重新赋值的body数据
        private String bodyJsonStr;

        public ModifyBodyHttpServletRequestWrapper(HttpServletRequest request, String bodyJsonStr) {
            super(request);
            this.bodyJsonStr = bodyJsonStr;
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            if(StringUtils.isEmpty(bodyJsonStr)) {
                bodyJsonStr = "";
            }
            // 必须指定utf-8编码，否则json请求数据中如果包含中文，会出现异常
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bodyJsonStr.getBytes("utf-8"));
            ServletInputStream servletInputStream = new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }
                @Override
                public boolean isReady() {
                    return false;
                }
                @Override
                public void setReadListener(ReadListener readListener) {
                }
                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
            };
            return servletInputStream;
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(this.getInputStream()));
        }

        public String getBodyJsonStr() {
            return bodyJsonStr;
        }

        public void setBodyJsonStr(String bodyJsonStr) {
            this.bodyJsonStr = bodyJsonStr;
        }
    }
}
